#ifndef PUDGE_GRFX_TRIMESH_HPP
#define PUDGE_GRFX_TRIMESH_HPP

#include <pudge/math/mat.hpp>

#include <vector>
#include <fstream>
#include <string>
#include <algorithm>

namespace pudge { namespace grfx {

	struct wavefront_obj_tag_ {} const wavefront_obj_tag ;

	template<class Real> struct trimesh
	{
		typedef unsigned long index_type;
		typedef pudge::math::vec<3,Real> position_type;
		typedef pudge::math::vec<3,Real> normal_type;
		typedef pudge::math::vec<2,Real> tcoord_type;

		struct index_traits
		{
			typedef index_type value_type;
			typedef STATIC_DRAW usage_type;
			typedef TRIANGLES poly_type;
		};

		struct position_traits
		{
			typedef position_type value_type;
			typedef STATIC_DRAW usage_type;
		};

		struct normal_traits
		{
			typedef normal_type value_type;
			typedef STATIC_DRAW usage_type;
		};

		struct tcoord_traits
		{
			typedef tcoord_type value_type;
			typedef STATIC_DRAW usage_type;
		};

		struct vertex
		{
			position_type position;
			normal_type normal;
			tcoord_type tcoord;
		};

		typedef std::vector<index_type> index_list;
		typedef std::vector<vertex> vertex_list;

		vertex_list vertices_;
		index_list indices_;

		vertex_list & vertices() { return vertices_; }
		index_list & indices() { return indices_; }

		template<class Iterator> Iterator skiphws(Iterator begin, Iterator end)
		{
			std::string HWS ("\v\t ");
			while(begin != end)
			{
				if(std::find(HWS.begin(), HWS.end(), *begin) != HWS.end())
					++begin;
				else
					return begin;
			}
			return end;
		}

		template
			<	class Iterator
			,	class Positions >
			void parse_wavefront_position
			(	Iterator begin
			,	Iterator end
			,	Positions & positions )
		{
			typedef position_type::value_type Val;
			position_type pos;
			char * bptr = &(*begin);
			pos.x = (Val)strtod(bptr, &bptr);
			pos.y = (Val)strtod(bptr, &bptr);
			pos.z = (Val)strtod(bptr, &bptr);
			positions.push_back(pos);
		}

		template
			<	class Iterator
			,	class Normals >
			void parse_wavefront_normal
			(	Iterator begin
			,	Iterator end
			,	Normals & normals )
		{
			typedef normal_type::value_type Val;
			normal_type nor;
			char * bptr = &(*begin);
			nor.x = (Val)strtod(bptr, &bptr);
			nor.y = (Val)strtod(bptr, &bptr);
			nor.z = (Val)strtod(bptr, &bptr);
			normals.push_back(nor);
		}

		template
			<	class Iterator
			,	class Tcoords >
			void parse_wavefront_tcoord
			(	Iterator begin
			,	Iterator end
			,	Tcoords & tcoords )
		{
			typedef tcoord_type::value_type Val;
			tcoord_type tc;
			char * bptr = &(*begin);
			tc.s = (Val)strtod(bptr, &bptr);
			tc.t = (Val)strtod(bptr, &bptr);
			tcoords.push_back(tc);
		}

		template
			<	class Iterator
			,	class Positions
			,	class Normals
			,	class Tcoords >
			void parse_wavefront_face
			(	Iterator begin
			,	Iterator end
			,	Positions & positions
			,	Normals & normals
			,	Tcoords & tcoords )
		{
			index_type 	pos_a, pos_b, pos_c
				,		nor_a, nor_b, nor_c
				,		tc_a, tc_b, tc_c;

			vertex vert_a, vert_b, vert_c;

			char * bptr = &(*begin);

			pos_a = (index_type)strtol(bptr,&bptr,10);
			if(*begin = '/')
			{
				if(*++bptr != '/')
					tc_a = (index_type)strtol(bptr,&bptr,10);
				nor_a = (index_type)strtol(++bptr,&bptr,10);
			}

			pos_b = (index_type)strtol(bptr,&bptr,10);
			if(*bptr = '/')
			{
				if(*++bptr != '/')
					tc_b = (index_type)strtol(bptr,&bptr,10);
				nor_b = (index_type)strtol(++bptr,&bptr,10);
			}

			pos_c = (index_type)strtol(bptr,&bptr,10);
			vert_a.position = positions[pos_a - 1];
			vert_b.position = positions[pos_b - 1];
			vert_c.position = positions[pos_c - 1];

			if(*bptr = '/')
			{
				if(*++bptr != '/')
				{
					tc_c = (index_type)strtol(bptr,&bptr,10);
					vert_a.tcoord = tcoords[tc_a - 1];
					vert_b.tcoord = tcoords[tc_b - 1];
					vert_c.tcoord = tcoords[tc_c - 1];
				}
				nor_c = (index_type)strtol(++bptr,&bptr,10);
				vert_a.normal = normals[nor_a - 1];
				vert_b.normal = normals[nor_b - 1];
				vert_c.normal = normals[nor_c - 1];
			}

			vertices_.push_back(vert_a);
			vertices_.push_back(vert_b);
			vertices_.push_back(vert_c);
		}

		template
			<	class Iterator
			,	class Positions
			,	class Normals
			,	class Tcoords >
			void parse_wavefront_line
			(	Iterator begin
			,	Iterator end
			,	Positions & positions
			,	Normals & normals
			,	Tcoords & tcoords )
		{
			typedef std::string Str;

			const Str F("f");
			const Str V("v");
			const Str VN("vn");
			const Str VT("vt");

			if(std::equal(F.begin(), F.end(), begin))
				parse_wavefront_face(begin + F.size(), end, positions, normals, tcoords);

			if(std::equal(V.begin(), V.end(), begin))
				parse_wavefront_position(begin + V.size(), end, positions);

			if(std::equal(VN.begin(), VN.end(), begin))
				parse_wavefront_normal(begin + VN.size(), end, normals);

			if(std::equal(VT.begin(), VT.end(), begin))
				parse_wavefront_tcoord(begin + VT.size(), end, tcoords);
		}

		trimesh(std::string const& fname, wavefront_obj_tag_)
		{
			std::vector<position_type> positions;
			std::vector<normal_type> normals;
			std::vector<tcoord_type> tcoords;

			std::fstream fs (fname.c_str());

			if(fs.fail()) throw -1;

			while(!fs.eof())
			{
				std::string line;
				std::getline(fs,line);

				if(line.size() == 0) 
					continue;

				parse_wavefront_line
					(	line.begin()
					,	line.end()
					,	positions
					,	normals
					,	tcoords );
			}

			for(unsigned long x = 0; x < vertices_.size(); ++x)
				indices_.push_back(x);
		}
	};

	struct stream_trimesh_
	{
		template<class Trimesh> struct streamer
		{
			Trimesh & trimesh_;
			streamer(Trimesh & t) : trimesh_(t) {}

			template<class IndStream, class PosStream, class NorStream, class TcStream>
			void operator ()(IndStream & is, PosStream & ps, NorStream & ns, TcStream & ts)
			{
				typedef Trimesh::index_list LL;
				typedef Trimesh::vertex_list VL;

				LL & ll = trimesh_.indices();
				VL & vl = trimesh_.vertices();

				for(LL::iterator i = ll.begin(); i != ll.end(); ++i)
					is << (*i);

				for(VL::iterator i = vl.begin(); i != vl.end(); ++i)
				{
					VL::value_type & v = (*i);
					ps << v.position;
					ns << v.normal;
					ts << v.tcoord;
				}
			}
		};
		template<class Trimesh> streamer<Trimesh> operator ()(Trimesh & tri) const
		{
			return streamer<Trimesh>(tri);
		}
	} const stream_trimesh;

	struct trimesh_test_
	{
		void operator()() const
		{
			vertex_shader vshader ("../../../src/shaders/basic_shader.vert", file_tag);
			frag_shader fshader ("../../../src/shaders/basic_shader.frag", file_tag);
			shader_program prog;

			bind_attribute(prog, std::string("position"));
			bind_attribute(prog, std::string("normal"));
			bind_attribute(prog, std::string("tcoord"));
			bind_shader(prog, vshader);
			bind_shader(prog, fshader);
			link_program(prog);
			use_program(prog);

			// setup multitexture sampler uniform
			long multi = 0;
			uniform sampler(prog, "tex");
			set_uniform(sampler, multi);

			// setup modelview and projection uniform
			pudge::math::mat<float> modelview, projection;

			modelview.set_translation(0.0f, -1.0f, -3.0f);
			projection = pudge::math::perspective<float>();

			uniform mvuni (prog, "modelview");
			uniform pruni (prog, "projection");

			set_uniform(mvuni, modelview);
			set_uniform(pruni, projection);

			// setup simple lighting
			pudge::math::vec<4,float> ambiant, color, dlvec;
			ambiant(0.5f,0.5f,0.5f,1.0f);
			color(0.0f,0.5f,0.0f,1.0f);
			dlvec(0.0f,-1.0f,-0.3f,1.0f);
			pudge::math::normalize(dlvec, dlvec);

			uniform ambuni (prog, "ambiant");
			uniform coluni (prog, "dl0color");
			uniform dluni (prog, "dl0vec");

			set_uniform(ambuni, ambiant);
			set_uniform(coluni, color);
			set_uniform(dluni, dlvec);

			// setup trimesh
			trimesh<float> tm ("../../../media/cube.obj", wavefront_obj_tag);
			
			index_attribute<trimesh<float>::index_traits> indices;
			vertex_attribute<trimesh<float>::position_traits> positions;
			vertex_attribute<trimesh<float>::normal_traits> normals;
			vertex_attribute<trimesh<float>::tcoord_traits> tcoords;

			unsigned int icnt = tm.indices().size();
			unsigned int vcnt = tm.vertices().size();

			glEnable(GL_DEPTH_TEST);
			cache_attributes(indices, positions, normals, tcoords, stream_trimesh(tm), icnt, vcnt);
			render_attributes(icnt, indices, positions, normals, tcoords);

			decache_attribute(indices);
			decache_attribute(positions);
			decache_attribute(normals);
			decache_attribute(tcoords);
			//decache_texture(tex);
		}
	} const trimesh_test;
}}

#endif